﻿#include	"Engine.Script.hpp"

#include	<memory>

namespace Engine { namespace Lua {

	void lua_rawgetfield(lua_State * pL, int nIdx, char const * pKey) {
		nIdx = lua_absindex(pL, nIdx);
		lua_pushstring(pL, pKey);
		lua_rawget(pL, nIdx);
	}

	void lua_rawsetfield(lua_State * pL, int nIdx, char const * pKey) {
		nIdx = lua_absindex(pL, nIdx);
		lua_pushstring(pL, pKey);
		lua_insert(pL, -2);
		lua_rawset(pL, nIdx);
	}

	bool lua_isfulluserdata(lua_State * pL, int nIdx) {
		return (lua_isuserdata(pL, nIdx) && !lua_islightuserdata(pL, nIdx));
	}

	void lua_printerror(lua_State * pL) {
		if (lua_gettop(pL) <= 0) lua_pushstring(pL, "Unknown error");
		lua_getglobal(pL, "print");
		lua_pushvalue(pL, -2);
		lua_pcall(pL, 1, 0, 0);
		lua_pop(pL, 1);
	}

	int TraceBackProxy::Proc(lua_State * pL) {
		lua_getglobal(pL, "debug");
		lua_getfield(pL, -1, "traceback");
		lua_pushvalue(pL, 1);
		lua_pushinteger(pL, 2);
		lua_call(pL, 2, 1);
		return 1;
	}

	int ClassExtensionProxy::Proc(lua_State * pL) {
		const char * szClass = lua_tostring(pL, -1);
		luaL_getmetatable(pL, szClass);
		if (lua_isnil(pL, -1)) luaL_error(pL, "Class '%s' not registered!", szClass);
		return 1;
	}

	int RequireProxy::Proc(lua_State * pL) {
		if (!lua_islightuserdata(pL, lua_upvalueindex(1))) {
			luaL_error(pL, "require must based on a lua_State!");
			return 0;
		} else if (!lua_isstring(pL, -1)) {
			luaL_error(pL, "require bad param!");
			return 0;
		}

		Script * pScript = static_cast<Script *>(lua_touserdata(pL, lua_upvalueindex(1)));
		if (!pScript) {
			luaL_error(pL, "upvalue for require is not an instance of Engine::Lua::Script!");
			return 0;
		}

		const char * pFile = lua_tostring(pL, -1);
		lua_pop(pL, -1);

		int nTop = lua_gettop(pL);
		pScript->DoFile(pFile, false);
		return lua_gettop(pL) - nTop;
	}

	int ReadOnlyProxy::Proc(lua_State * pL) {
		return luaL_error(pL, "%s is read-only", lua_tostring(pL, lua_upvalueindex(1)));
	}

	int JumperProxy::Proc(lua_State * pL) {
		lua_printerror(pL);
		lua_gc(pL, LUA_GCCOLLECT, 0);
		throw std::runtime_error("Script Error! Just for long-jump!");
	}

	int MetaTableProxy::Index(lua_State * pL) {
		int nRes = 0;
		lua_getmetatable(pL, 1);
		while (true) {
			lua_pushvalue(pL, 2);
			lua_rawget(pL, -2);
			if (lua_isnil(pL, -1)) {
				lua_pop(pL, 1);
				lua_rawgetfield(pL, -1, "__propget");
				lua_pushvalue(pL, 2);
				lua_rawget(pL, -2);
				lua_remove(pL, -2);
				if (lua_iscfunction(pL, -1)) {
					lua_remove(pL, -2);
					lua_pushvalue(pL, 1);
					lua_call(pL, 1, 1);
					nRes = 1;
					break;
				} else if (lua_isnil(pL, -1)) {
					lua_pop(pL, 1);
					lua_rawgetfield(pL, -1, "__parent");
					lua_remove(pL, -2);
					if (lua_istable(pL, -1)) {
						continue;
					} else if (lua_isnil(pL, -1)) {
						nRes = 1;
						break;
					} else {
						break;
					}
				} else {
					break;
				}
			} else if (lua_istable(pL, -1) || lua_iscfunction(pL, -1)) {
				lua_remove(pL, -2);
				nRes = 1;
				break;
			} else {
				break;
			}
		}

		return nRes;
	}

	int MetaTableProxy::NewIndex(lua_State * pL) {
		int nRes = 0;
		lua_getmetatable(pL, 1);
		while (true) {
			lua_rawgetfield(pL, -1, "__propset");
			if (!lua_istable(pL, -1)) break;
			lua_pushvalue(pL, 2);
			lua_rawget(pL, -2);
			lua_remove(pL, -2);
			if (lua_iscfunction(pL, -1)) {
				lua_remove(pL, -2);
				lua_pushvalue(pL, 3);
				lua_call(pL, 1, 0);
				nRes = 0;
				break;
			} else if (lua_isnil(pL, -1)) {
				lua_pop(pL, 1);
				lua_rawgetfield(pL, -1, "__parent");
				if (lua_istable(pL, -1)) {
					lua_remove(pL, -2);
					continue;
				} else if (lua_isnil(pL, -1)) {
					lua_pop(pL, 2);
					nRes = luaL_error(pL, "No writable variable named %s!", lua_tostring(pL, 2));
					break;
				} else {
					break;
				}
			} else {
				break;
			}
		}
		return nRes;
	}

	int MetaTableProxy::Func(lua_State * pL) {
		int nIdx = lua_upvalueindex(1);
		if (!lua_islightuserdata(pL, nIdx)) {
			luaL_error(pL, "Call func error!");
			return 0;
		}

		typedef int(*CFunc)(Stack &);
		CFunc fnOpt = (CFunc)(lua_touserdata(pL, lua_upvalueindex(1)));
		if (!fnOpt) luaL_error(pL, "Try to call an undefined method!");
		return StackCaller::Call(pL, fnOpt);
	}

	Reference::Reference(lua_State * pL /* = 0 */, int nIdx /* = LUA_NOREF */)
		: _pL(pL), _nIdx(nIdx) {}

	Reference::Reference(const Reference & rIns)
		: _pL(rIns._pL), _nIdx(LUA_NOREF) {
		lua_rawgeti(_pL, LUA_REGISTRYINDEX, rIns._nIdx);
		_nIdx = luaL_ref(_pL, LUA_REGISTRYINDEX);
	}

	Reference::~Reference() { Release(); }

	Reference & Reference::operator=(const Reference & rIns) {
		if (_pL && (_nIdx != LUA_NOREF)){
			luaL_unref(_pL, LUA_REGISTRYINDEX, _nIdx);
			_pL = 0;
			_nIdx = LUA_NOREF;
		}

		if (rIns._pL && (rIns._nIdx != LUA_NOREF)){
			lua_rawgeti(rIns._pL, LUA_REGISTRYINDEX, rIns._nIdx);
			_pL = rIns._pL;
			_nIdx = luaL_ref(_pL, LUA_REGISTRYINDEX);
		}

		return *this;
	}

	int Reference::Type() {
		if (_nIdx == LUA_NOREF) return LUA_TNIL;
		Push();
		Cleaner iCleaner(_pL, 1);
		return lua_type(_pL, -1);
	}

	void Reference::Push() {
		if (_pL && (_nIdx != LUA_NOREF))
			lua_rawgeti(_pL, LUA_REGISTRYINDEX, _nIdx);
	}

	void Reference::Pop() {
		luaL_unref(_pL, LUA_REGISTRYINDEX, _nIdx);
		_nIdx = luaL_ref(_pL, LUA_REGISTRYINDEX);
	}

	void Reference::Release() {
		if (_pL && (_nIdx != LUA_NOREF)){
			luaL_unref(_pL, LUA_REGISTRYINDEX, _nIdx);
			_pL = 0;
			_nIdx = LUA_NOREF;
		}
	}

	Stack::Stack(lua_State * pL, bool bNeedPop /* = true */)
		: _pL(pL), _bNeedPop(bNeedPop) {}

	Stack::~Stack() {
		if (_bNeedPop) lua_pop(_pL, lua_gettop(_pL));
	}

	int Stack::Top() {
		return lua_gettop(_pL);
	}

	void Stack::Pop(int nCount) {
		int nTop = lua_gettop(_pL);
		lua_pop(_pL, (nCount > nTop ? nTop : nCount));
	}

	const char * Stack::TypeName(int nIdx) {
		if (lua_absindex(_pL, nIdx) > lua_gettop(_pL)) return nullptr;
		return lua_typename(_pL, lua_type(_pL, nIdx));
	}

	lua_State * Stack::GetCState() {
		return _pL;
	}

	GlobalRegister & GlobalRegister::AddFunction(const char * szFunc, int(*fnOpt)(Stack &)) {
		Push();
		lua_pushlightuserdata(_pL, (void *)fnOpt);
		lua_pushcclosure(_pL, &MetaTableProxy::Func, 1);
		lua_rawsetfield(_pL, -2, szFunc);
		lua_pop(_pL, 1);
		return *this;
	}

	Table::Table(lua_State * pL /* = 0 */, int nRef /* = LUA_NOREF */)
		: Reference(pL, nRef) {
		if (_pL && _nIdx != LUA_NOREF) {
			int nType = Type();
			if (nType != LUA_TTABLE) {
				luaL_error(_pL, "Table expected but got %s!", lua_typename(_pL, nType));
			}
		}
	}

	Table::Table(const Reference & iIns)
		: Reference(iIns) {
		if (_pL && _nIdx != LUA_NOREF) {
			int nType = Type();
			if (nType != LUA_TTABLE) {
				luaL_error(_pL, "Table expected but got %s!", lua_typename(_pL, nType));
			}
		}
	}

	Table::Table(Stack & rStack) : Reference(rStack.GetCState()) {
		lua_newtable(_pL);
		_nIdx = luaL_ref(_pL, LUA_REGISTRYINDEX);
	}
	
	Script::Script()
		: _pL(luaL_newstate()) {
		/// Lua错误的跳转
		lua_atpanic(_pL, &JumperProxy::Proc);

		luaL_openlibs(_pL);
		lua_getglobal(_pL, "_G");

		/// 增加GetClass接口用于扩展C++向Lua中注册的对象
		lua_pushcfunction(_pL, &ClassExtensionProxy::Proc);
		lua_rawsetfield(_pL, -2, "GetClass");

		/// 重载require函数，用于统一加载
		lua_pushlightuserdata(_pL, this);
		lua_pushcclosure(_pL, &RequireProxy::Proc, 1);
		lua_rawsetfield(_pL, -2, "require");
		lua_pop(_pL, 1);
	}

	Script::~Script() {
		if (_pL) lua_close(_pL);
	}

	Script & Script::Instance() {
		static std::unique_ptr<Script> _iIns;
		if (_iIns.get() == nullptr) _iIns.reset(new Script());
		return *(_iIns.get());
	}

	void Script::DoFile(const char * szFile, bool bForce /* = false */) {
		std::string szPath = szFile;
		for (size_t nIdx = 0; nIdx < szPath.length(); ++nIdx)
			if (szPath[nIdx] == '\\') szPath[nIdx] = '/';

		if (std::find(_vLoaded.begin(), _vLoaded.end(), szPath) != _vLoaded.end() && !bForce)
			return;

		_vLoaded.push_back(szPath);

		if (0 != luaL_loadfile(_pL, szPath.c_str())) {
			lua_printerror(_pL);
			return;
		}

		int nFuncIdx = lua_gettop(_pL);
		lua_pushcfunction(_pL, &TraceBackProxy::Proc);
		lua_insert(_pL, nFuncIdx);
		int nCall = lua_pcall(_pL, 0, LUA_MULTRET, nFuncIdx);
		lua_remove(_pL, nFuncIdx);
		if (nCall != 0) lua_printerror(_pL);
	}

	void Script::DoString(const char * szCode) {
		int nCall = 0;

		if (0 != luaL_loadstring(_pL, szCode)) {
			lua_printerror(_pL);
			return;
		}

		int nFuncIdx = lua_gettop(_pL);
		lua_pushcfunction(_pL, &TraceBackProxy::Proc);
		lua_insert(_pL, nFuncIdx);
		nCall = lua_pcall(_pL, 0, LUA_MULTRET, nFuncIdx);
		lua_remove(_pL, nFuncIdx);
		if (nCall != 0) lua_printerror(_pL);
	}

	GlobalRegister Script::Register(const char * szSpace) {
		lua_getglobal(_pL, szSpace);
		if (!lua_isnil(_pL, -1)) {
			return GlobalRegister(_pL, luaL_ref(_pL, LUA_REGISTRYINDEX));
		}

		lua_newtable(_pL);
		lua_newtable(_pL);
		lua_pushcfunction(_pL, &MetaTableProxy::Index);
		lua_rawsetfield(_pL, -2, "__index");
		lua_pushcfunction(_pL, &MetaTableProxy::NewIndex);
		lua_rawsetfield(_pL, -2, "__newindex");
		lua_newtable(_pL);
		lua_rawsetfield(_pL, -2, "__propget");
		lua_newtable(_pL);
		lua_rawsetfield(_pL, -2, "__propset");
		lua_setmetatable(_pL, -2);
		lua_setglobal(_pL, szSpace);

		lua_getglobal(_pL, szSpace);
		return GlobalRegister(_pL, luaL_ref(_pL, LUA_REGISTRYINDEX));
	}

	Stack Script::GetStack() {
		return Stack(_pL, false);
	}

}}
